package game.evolver;

import game.Game;

import org.jgap.*;

/**
 * Fitness function for the DeckEvolver example.
 *
 * @author Klaus Meffert
 * @since 2.3
 */
public class DeckEvolverFitnessFunction extends FitnessFunction {

  private final double targetNumberOfTurns;

  public static final double MAX_BOUND = 100000.0d;

  private static final double ZERO_DIFFERENCE_FITNESS = Math.sqrt(MAX_BOUND);

  public DeckEvolverFitnessFunction() {
	  targetNumberOfTurns = 1;
  }

  /**
   * Determine the fitness of the given Chromosome instance. The higher the
   * return value, the more fit the instance. This method should always
   * return the same fitness value for two equivalent Chromosome instances.
   *
   * @param a_subject the Chromosome instance to evaluate
   * @return a positive double reflecting the fitness rating of the given
   * Chromosome
   *
   * @author Klaus Meffert
   * @since 2.3
   */
  public double evaluate(IChromosome a_subject) {
    // The fitness value measures both how close the value is to the
    // target amount supplied by the user and the total number of items
    // represented by the solution. We do this in two steps: first,
    // we consider only the represented amount of change vs. the target
    // amount of change and return higher fitness values for amounts
    // closer to the target, and lower fitness values for amounts further
    // away from the target. Then we go to step 2, which returns a higher
    // fitness value for solutions representing fewer total items, and
    // lower fitness values for solutions representing more total items.
    // ------------------------------------------------------------------
    int numberOfCards = getTotalNumberOfCards(a_subject);
    
    if (numberOfCards< 56 || numberOfCards > 66) {
    	return 1.0d; // no sense in playing any games with a very bad deck
    }
    
    double numberOfTurns = getNumberOfTurns(a_subject);

    double numberOfTurnsDifference = Math.abs(targetNumberOfTurns - numberOfTurns);
    double fitness = 0.0d;
    // Step 1: Determine distance of amount represented by solution from
    // the target amount. If the change difference is greater than zero we
    // will divide one by the difference in change between the
    // solution amount and the target amount. That will give the desired
    // effect of returning higher values for amounts closer to the target
    // amount and lower values for amounts further away from the target
    // amount.
    // In the case where the change difference is zero it means that we have
    // the correct amount and we assign a higher fitness value
    // -----------------------------------------------------------------
    fitness += numberOfTurnsDifferenceBonus(MAX_BOUND, numberOfTurnsDifference);
    // Step 2: We divide the fitness value by a penalty based on the number of
    // items. The higher the number of items the higher the penalty and the
    // smaller the fitness value.
    // And inversely the smaller number of items in the solution the higher
    // the resulting fitness value.
    // -----------------------------------------------------------------------
    fitness -= computeCardNumberPenalty(MAX_BOUND, numberOfCards);
    // Make sure fitness value is always positive.
    // -------------------------------------------
    return Math.max(1.0d, fitness);
  }

  /**
   * Bonus calculation of fitness value.
   * @param a_maxFitness maximum fitness value appliable
   * @param numberOfTurnsAwayFrom1 volume difference in ccm for the items problem
   * @return bonus for given volume difference
   *
   * @author Klaus Meffert
   * @since 2.3
   */
  protected double numberOfTurnsDifferenceBonus(double a_maxFitness, double numberOfTurnsAwayFrom1) {
	  if (numberOfTurnsAwayFrom1 == 0) {
		  return a_maxFitness;
	  } else {
		  return a_maxFitness - 2*(numberOfTurnsAwayFrom1 * numberOfTurnsAwayFrom1 * numberOfTurnsAwayFrom1 * numberOfTurnsAwayFrom1);
	  }
  }

  /**
   * Calculates the penalty to apply to the fitness value based on the amount
   * of items in the solution.
   *
   * @param a_maxFitness upper boundary for fitness value possible
   * @param a_items number of items in the solution
   * @return a penalty for the fitness value based on the number of items
   *
   * @author Klaus Meffert
   * @since 2.3
   */
  protected double computeCardNumberPenalty(double a_maxFitness, int a_items) {
	  if (a_items == 60) {
		  return 1.0d;
	  }
	  // small nudge towards 60 cards.
	  else if (a_items > 60) {   	
		  return (Math.min(a_maxFitness, a_items * a_items * a_items));
	  }
	  else { // less than 60 is really bad
		  return (Math.min(a_maxFitness, a_items * a_items * a_items * a_items * a_items));
	  }

  }

  public static double getNumberOfTurns(IChromosome a_potentialSolution) {
    double numberOfTurns = 0.0d;
    int[] quantity = new int[DeckEvolverMain.cardNames.length];
    for (int i=0; i< quantity.length; i++) {
    	quantity[i] = getNumberOfItemsAtGene(a_potentialSolution, i);
    }
    Game g = new Game();
    return g.playGameSeries(DeckEvolverMain.cardNames, quantity);
  }

  /**
   * Retrieves the number of items represented by the given potential
   * solution at the given gene position.
   *
   * @param a_potentialSolution the potential solution to evaluate
   * @param a_position the gene position to evaluate
   * @return the number of items represented by the potential solution
   * at the given gene position
   *
   * @author Klaus Meffert
   * @since 2.3
   */
  public static int getNumberOfItemsAtGene(IChromosome a_potentialSolution,
                                           int a_position) {
    Integer numItems =
        (Integer) a_potentialSolution.getGene(a_position).getAllele();
    return numItems.intValue();
  }

  /**
   * Returns the total number of items represented by all of the genes in
   * the given potential solution.
   *
   * @param a_potentialSolution the potential solution to evaluate
   * @return the total number of items represented by the given Chromosome
   *
   * @author Klaus Meffert
   * @since 2.3
   */
  public static int getTotalNumberOfCards(IChromosome a_potentialSolution) {
    int totalItems = 0;
    int numberOfGenes = a_potentialSolution.size();
    for (int i = 0; i < numberOfGenes; i++) {
      totalItems += getNumberOfItemsAtGene(a_potentialSolution, i);
    }
    return totalItems;
  }
}

